0. 实现 MVVM 三要素
将 mvvm
作为中间层,处理数据变化后的回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const mvvm = { callbacks: {}, pub(prop, value) { (this.callbacks[prop] || []).forEach(callback => callback(prop, value)) }, sub(prop, callback = () => {}) { this.callbacks[prop] = this.callbacks[prop] || [] this.callbacks[prop].push(callback) } }
|
MVVM 实现的三要素:
将 Model
绑定到对应的 View
(比如可以通过 h5 data-* 属性绑定),即 bind(Model, view)
1 2
| <input data-bind='name' /> <input data-bind='age' />
|
View
的改变会触发绑定的 Model
变化(比如监听元素的 change 事件),即 Model = f(View)
1 2 3 4 5 6 7 8 9 10 11 12 13
| const changeCb = e => { const bindProp = e.target.dataset.bind const value = e.target.value bindProp && mvvm.pub(bindProp, value) } document.addEventListener('change', changeCb )
|
Model
的改变会触发绑定了该 Model
的 View
变化(下面会提及三种方式),即 View = g(Model)
1. 发布订阅模式
已 User Model
为例,当 User
属性变化后,通知 mvvm
执行回调:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const User = ({name,age}) => { const user = { get(prop) {return this[prop]}, set(prop, value) { this[prop] = value mvvm.pub(prop,value) } } user.set('name', name) user.set('age', age) return user }
|
demo
2-1. 发布订阅模式升级版-数据劫持
上述发布订阅模式在读取和写入 user
属性时,分别采用的是 getter
和 setter
方法,即
1 2 3 4 5
| const name = user.get('name') user.set(name, 'chongya')
|
当然也可以通过其他自定义方法来读写 user
的属性,但是我们更希望通过直接读写对象属性的方式在做双向绑定:
1 2 3 4
| const name = user.name user.name = 'chongya'
|
数据劫持的方式就是通过劫持对象属性的方式来监听数据变动,数据劫持可以通过如下方式实现:
- ES5
Object.defineProperty()
- ES6
new Proxy()
因此 User model
可以变为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| const user = {} Object.defineProperties(user, { __name: { writable: true, configurable: false, enumerable: false }, __age: { writable: true, configurable: false, enumerable: false }, name: { get() {return this.__name}, set(name) { mvvm.pub('name', name) this.__name = name } }, age: { get() {return this.__age}, set(age) { mvvm.pub('age', age) this.__age = age } } }) const user = new Proxy({}, { get(target, prop){ return Reflect.get(target, prop) }, set(target, prop, value){ mvvm.pub(prop, value) return Reflect.set(target, prop, value) } })
|
demo
2-2. Vue mvvm
将数据劫持 + 发布订阅模式进一步抽象化,参考 Vue 中双向绑定的实现,得到如下 Observer
、Compile
、Watcher
三个模块:

3. 脏值检测
一言蔽之,脏值检测就是在特定条件下轮询检测数据是否变动。
这里借鉴 Angular1 的实现方式:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| class Scope { constructor(initValue){ this.value = initValue this.$$watchers = [] } $watch(watchFn, listenerFn) { const watcher = { watchFn, listenerFn, last: () => {} } this.$$watchers.push(watcher) } $digest(){ let dirty = true while(dirty) { dirty = this.$digestOnce() } } $digestOnce(){ let newValue, oldValue, dirty this.$$watchers.forEach( watcher => { newValue = watcher.watchFn(this) oldValue = watcher.last if(newValue !== oldValue) { watcher.last = newValue watcher.listenerFn(newValue) dirty = true } } ) return dirty } } const user = new Scope({name: 'chongya', age: 20}) user.$watch( scope => scope.value.name, newName => mvvm.pub('name', newName) ) user.$watch( scope => scope.value.age, newAge => mvvm.pub('age', newAge) )
|
demo